/*
 * ethernet.cpp
 *
 *  Created on: May 25, 2018
 *      Author: yuantao
 */
#include "ethernet.h"
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <pthread.h>
#include "msgList.h"
#include <stdio.h>


CEthernet::CEthernet()
{
	m_sockRemote = -1;
	m_sockLocal = -1;
    memset(&m_stConnConfig,0,sizeof(connection_config));
}

CEthernet::~CEthernet()
{
	stopConnection();
}

void CEthernet::setConnectionConfig(connection_config * pstConnConfig)
{
    memcpy(&m_stConnConfig,pstConnConfig,sizeof(connection_config));
}

int CEthernet::startConnection(int isClient,int ip_protocol)
{
	struct sockaddr_in zeroIp;
	memset(&zeroIp, 0, sizeof(struct sockaddr_in));
    int flags = 0;

	short p_proto = IPPROTO_TCP;
	int type;// Ì×œÓ×ÖÃèÊö·û
    char buff[1024];
	if (ip_protocol == PROTOCOL_UDP)
	{
		p_proto = IPPROTO_UDP;
		type = SOCK_DGRAM;
	}
	else
	{
		p_proto = IPPROTO_TCP;
		type = SOCK_STREAM;
	}


	m_sockLocal = socket(PF_INET, type, p_proto);
	if (-1 == m_sockLocal)
	{
		return 2;
	}

	//set socket to none-blocking
    flags = fcntl(m_sockLocal,F_GETFL,0);
    fcntl(m_sockLocal,F_SETFL,flags|O_NONBLOCK);

	if (ip_protocol == PROTOCOL_UDP)
	{
		//UDP communication need no connection
		sockaddr_in RecvAddr;

		RecvAddr.sin_family = AF_INET;
		RecvAddr.sin_port = m_stConnConfig.localAddr.sin_port;
		RecvAddr.sin_addr.s_addr = m_stConnConfig.localAddr.sin_addr.s_addr;

		bind(m_sockLocal, (struct sockaddr *)&RecvAddr, sizeof(RecvAddr));

		m_isClient = 1;
		m_ip_protocol = PROTOCOL_UDP;

		int retCreate = pthread_create(&readUdpData_ThreadHandle,NULL,threadFunc_recvUdpData,this);
		if(retCreate == 0)
		{
            cout<<"start read udp data thread success\n"<<endl;
		}
		else
		{
			cout<<"failed to start read udp data thread,error:\n"<<retCreate;
		}

		return NO_ERROR;
	}

	int compRet = memcmp(&m_stConnConfig.remoteAddr, &zeroIp, sizeof(struct sockaddr_in));

	if (compRet == 0)
	{
		return 1;
	}

	if(isClient == 1)
	{
		//work as client, for tcp communication, connect to server
		int ret = connect(m_sockLocal, (struct sockaddr*)&m_stConnConfig.remoteAddr, sizeof(struct sockaddr_in));
		if (ret == -1)
		{
			//not completed due to nonblocking
			if (errno == EINPROGRESS)
			{
				fd_set writefds;
				fd_set readfds;
				fd_set exceptfds;
				FD_ZERO(&readfds);
				FD_ZERO(&exceptfds);
				FD_ZERO(&writefds);
				FD_SET(m_sockLocal, &writefds);
				struct timeval timeoutVal;
				timeoutVal.tv_usec = 3000;
				timeoutVal.tv_sec = 5;
				int selRet = select(
					1,
					&readfds,
					&writefds,
					&exceptfds,
					&timeoutVal
					);

				if (-1 == selRet)
				{
					return 4;
				}

				if (0 == selRet)
				{
					return 5;
				}

				if (selRet == 1)
				{
					m_isClient = 1;
					m_ip_protocol = PROTOCOL_TCP;
					return NO_ERROR;
				}
			}
			else
			{
				//listen sock corrupt
				return 1;
			}
			return 3;
		}
	}
	else
	{
	    //work as server
	    struct sockaddr_in  servaddr;
     memset(&servaddr, 0, sizeof(servaddr));
     servaddr.sin_family = AF_INET;
     servaddr.sin_addr.s_addr = htonl(0);
     servaddr.sin_port = htons(8084);//htons(m_stConnConfig.localAddr.sin_port);
     //printf("ip[%x],port[%d]\n",INADDR_ANY,m_stConnConfig.localAddr.sin_port);
     if( bind(m_sockLocal, (struct sockaddr*)&servaddr, sizeof(servaddr)) == -1){
         printf("bind socket error: %s(errno: %d)\n",strerror(errno),errno);
         return 0;
     }
 
     if( listen(m_sockLocal, 10) == -1){
         printf("listen socket error: %s(errno: %d)\n",strerror(errno),errno);
         return 0;
     }

     int flags = fcntl(m_sockLocal, F_GETFL, 0); 
     fcntl(m_sockLocal, F_SETFL, flags | O_NONBLOCK);

     int connfd;
     int recvNum;
     printf("======waiting for client's request======\n");
     list<clientInfo>::iterator itRemoteSock,itTmp;
     struct sockaddr_in addr_client;
     clientInfo stClient;
     msg_recv * oneMsgSend = NULL;
     unsigned int lenAddr= 0;
     int count = 0; 
     while(1){
         if( (connfd = accept(m_sockLocal, (struct sockaddr*)&addr_client, &lenAddr)) == -1){
             //printf("accept socket error: %s(errno: %d)",strerror(errno),errno);
             //continue;
         }
         else
         {
             printf("client connected,fd[%d],ip[%d],port[%d]\n",connfd,addr_client.sin_addr.s_addr,addr_client.sin_port);
             stClient.fd = connfd;
             stClient.ip = addr_client.sin_addr.s_addr;
             stClient.port = addr_client.sin_port;

             flags = fcntl(connfd, F_GETFL, 0); 
             fcntl(connfd, F_SETFL, flags | O_NONBLOCK);

             listClient.push_back(stClient);             
         }

         itRemoteSock = listClient.begin();
         memset(buff,0,1024);
         while(itRemoteSock!=listClient.end())
         {
             recvNum = recv(itRemoteSock->fd, buff, 1024, 0);
             if(recvNum>0)
             {
                 //data is received
                 if((UPDATE_IP_CODE==buff[0])||(ACK_CODE==buff[0]))
                 {
                     g_ptrRecvPool->addOneMsg(recvNum,buff,itRemoteSock->ip,itRemoteSock->port,itRemoteSock->fd,1);
                 }
                 //send(itRemoteSock->fd,"recv echo",9,0);
             }
             else if(recvNum==0)
             {//the socket has closed;
                itTmp = itRemoteSock;
                itRemoteSock++;
                printf("client closed,fd[%d],ip[%d],port[%d]\n",itTmp->fd,itTmp->ip,itTmp->port);
                close(itTmp->fd);
                listClient.erase(itTmp);
                continue;
             }
             else
             {
                 //some error ocurred
             }
             itRemoteSock++;             
         }

        oneMsgSend = g_ptrRecvPool->getOneMsg(0);
		if(oneMsgSend!=NULL)
		{
		   cout << "send tcp in thread"<<endl;
		   //pUdpConnection->sendUdpData(oneMsgSend->buff,oneMsgSend->msgLen,oneMsgSend->peerIp,oneMsgSend->peerPort); 
           
           if(oneMsgSend->fd > 0)
           {
               send(oneMsgSend->fd,oneMsgSend->buff,oneMsgSend->msgLen,0);
           }
           free(oneMsgSend);
		   oneMsgSend = NULL;
		}
		count++;
		if(count>2000)
		{
		   //printf("server is running\n");
		   count = 0;
		}
        usleep(1000);
      }

      cleanTcpClients();
	}
	return NO_ERROR;
}

int CEthernet::stopConnection(void)
{
	if(m_sockLocal != -1)
	{
       close(m_sockLocal);
       m_sockLocal = -1;
	}

	if(m_sockRemote != -1)
	{
        close(m_sockRemote);
        m_sockRemote = -1;
	}

	memset(&m_stConnConfig,0,sizeof(connection_config));

	return 0;
}

int CEthernet::recvUdpData(char * buffOut,unsigned int maxLengthIn,unsigned int * peerIp, unsigned short * peerPort)
{
	int ret = 0;
    struct sockaddr_in peer_addr;
    socklen_t peer_addr_len = sizeof(struct sockaddr_in);
    memset(&peer_addr,0,peer_addr_len);
    ret = recvfrom(m_sockLocal, buffOut, maxLengthIn, 0,
            (struct sockaddr *) &peer_addr, &peer_addr_len);

	*peerIp = peer_addr.sin_addr.s_addr;
	*peerPort = peer_addr.sin_port;

    return ret;
}

int CEthernet::sendUdpData(char * buffIn,unsigned int buffLen,unsigned int peerIp, unsigned short peerPort)
{
    struct sockaddr_in peer_addr;
	peer_addr.sin_family=AF_INET;
    peer_addr.sin_port=peerPort;
	peer_addr.sin_addr.s_addr = peerIp;
	int sendRet = sendto(m_sockLocal,buffIn,buffLen,0,(struct sockaddr *)&(peer_addr),sizeof(struct sockaddr));
    return sendRet;
}


void *threadFunc_recvUdpData(void *arg)
{
	CEthernet* pUdpConnection = (CEthernet*)arg;
	pUdpConnection->recvThreadRunningFlag = RUNNING;
	char * recvBuff = new char[MAX_BYTES_RECV_ONCE];
	int sizeRecv = MAX_BYTES_RECV_ONCE;
    memset(recvBuff,0,MAX_BYTES_RECV_ONCE);
        static int count = 0;
	unsigned int peerIp = 0;
	unsigned short peerport = 0;
	msg_recv * oneMsgSend = NULL;
    while(pUdpConnection->recvThreadRunningFlag == RUNNING)
    {
    	usleep(100);
        count++;
        if(count>=5000)
        {
           //cout<<"recv thread is running"<<endl;
           count = 0;
        }
    	
    	sizeRecv = pUdpConnection->recvUdpData(recvBuff,BYTES_SEND_ONCE,&peerIp,&peerport);
    	if(sizeRecv > 0)
    	{
           //cout<<"recv data, length:"<<sizeRecv<<endl;
           //sendto(m_sockLocal,"from test host",15,(struct sockaddr *)&(m_stConnConfig.remoteAddr),sizeof(struct sockaddr));
           //pUdpConnection->sendUdpData("from test host",15);
           //pUdpConnection->sendUdpData(recvBuff,sizeRecv);
		   //cout << "udp from ip:"<<peerIp<<", port:"<<peerport<<","<<recvBuff<<endl;
           if(strncmp(recvBuff,"fd",2)==0)
           {
               g_ptrRecvPool->addOneMsg(sizeRecv,recvBuff,peerIp,peerport,-1,1);
           }
		   
		   memset(recvBuff,0,MAX_BYTES_RECV_ONCE);
    	}

		
        /*oneMsgSend = g_ptrRecvPool->getOneMsg(0);
		if(oneMsgSend!=NULL)
		{
		   //cout << "send upd in thread"<<endl;
		   pUdpConnection->sendUdpData(oneMsgSend->buff,oneMsgSend->msgLen,oneMsgSend->peerIp,oneMsgSend->peerPort); 
           free(oneMsgSend);
		   oneMsgSend = NULL;
		}*/
    	
    }

    delete [] recvBuff;
    recvBuff = NULL;

    pUdpConnection->recvThreadRunningFlag = STOP_FINISHED;

    cout<<"recv udp data thread exit"<<endl;
    pthread_exit((void*)&(pUdpConnection->recvThreadExitVal));
    return &(pUdpConnection->recvThreadExitVal);

}

void CEthernet::joinRecvThread(void)
{
	int *ret_join = NULL;

    pthread_join(readUdpData_ThreadHandle,(void**)&ret_join);
}

int CEthernet::sendUdpData(char * buffIn,unsigned int buffLen)
{
    int sendRet = sendto(m_sockLocal,buffIn,buffLen,0,(struct sockaddr *)&(m_stConnConfig.remoteAddr),sizeof(struct sockaddr));
    return sendRet;
}

int startLocalUdp(CEthernet *pEthLocal)
{
    connection_config connConfig;
	memset(&connConfig,0,sizeof(connection_config));
	connConfig.localAddr.sin_port = htons(5500);
	struct in_addr tempInAddr;
	//value = subStringStart +strlen(strLocalIp) + 1;
	char strIp[16] = "127.0.0.1";
	
	if(inet_pton(AF_INET, strIp,&tempInAddr) == 1)
	{
		connConfig.localAddr.sin_addr.s_addr = tempInAddr.s_addr;
	}

	pEthLocal->setConnectionConfig(&connConfig);
    int startRet = pEthLocal->startConnection(1,PROTOCOL_UDP);

	return startRet;

}

void CEthernet::cleanTcpClients(void)
{
    list<clientInfo>::iterator itClient;
    itClient = listClient.begin();

    while(itClient!=listClient.end())
    {
        if(itClient->fd > 0)
        {
            close(itClient->fd);
        }
        itClient++;
    }

    listClient.clear();
}


